use super::{
    err, err_value, json, load_box, load_ready, load_ring, log_pack, ok,
    pbase::{PairBase, HS},
    save_data, throw_err, try_rm_task, update_side, update_stock, Base, Client, ECode, Lock, MapSv,
    RNull, Resp, TBase, VBase, VMap, Value, VecV,
};
use base::tbase::box_psn;
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub struct Pair;

impl Base for Pair {}
impl TBase for Pair {}

impl Pair {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "addbox" => self.do_addbox(line, data, client, lock).await,
            "appendbox" => self.do_appendbox(line, data, client, lock).await,
            "boxpack" => self.do_boxpack(line, data, client, lock).await,
            "calc" => self.do_calc(line, data, client, lock).await,
            "deploy" => self.do_deploy(line, data, client, lock).await,
            "find" => {
                let tp = data.ck_str("tp")?;
                let sn = data.ck_str("sn")?;

                let ret = self.find_psn(line, sn, tp, client).await?;

                ok!(ret)
            }
            "handbox" => self.do_handbox(line, client, lock).await,
            "info" => self.do_info(line, data, client).await,
            "join" => self.do_join(line, data, client, lock).await,
            "load" => {
                let k = data.ck_str("k")?;
                let sub = data.k_str("sub");
                let filter = data.k_array("v");

                let body = load_ready(k, sub, filter, line, client).await?;

                ok!(body)
            }
            "ng" => self.do_ng(line, data, client, lock).await,
            "nopair" => self.do_nopair(line, data, client).await,
            "nodata" => self.do_nodata(line, data, client).await,
            "pack" => self.do_pack(line, data, client, lock).await,
            "pbox" => {
                let mut pbox_outer = HashSet::new();
                let mut pbox_inner = HashSet::new();

                let usn = match data.k_str_empty_trim("usn") {
                    Some(v) => v,
                    None => "",
                };

                let _ = self
                    .get_pbox(line, usn, &mut pbox_outer, &mut pbox_inner, client)
                    .await?;

                ok!(json!({
                    "outer": pbox_outer,
                    "inner": pbox_inner,
                }))
            }
            "remove" => self.do_remove(line, data, client, lock).await,
            "rcalc" => self.do_repair_calc(line, data, client, lock).await,
            "rebind" => self.do_rebind(line, data, client, lock).await,
            "repair" => self.do_repair(line, data, client, lock).await,
            "sbox" => self.do_sbox(line, client).await,
            "try" => self.do_try(line, data, client, lock).await,
            "trydeploy" => self.do_trydeploy(line, data, client, lock).await,
            "unpsn" => self.do_unpsn(line, data, client, lock).await,
            _ => throw_err!(ECode::NoServe, "no pair serve"),
        }
    }

    #[instrument(skip_all, name = "down_mt")]
    async fn down_mt(&self, line: &str, psn_data: &MapSv, client: &Client) -> RNull {
        let psn = psn_data.ck_str("sn")?;

        warn!("pack log");

        if let Some(tsn) = psn_data.k_str_empty_trim("tech") {
            let _ = log_pack(line, "packlog", psn, tsn, client).await;
        }

        let mt = psn_data.ck_str("mt")?;
        let mt_mod = self.get_mt_type(mt)?;

        let side_mod = self.get_side_type("hand")?;

        if let Some(cage_sn) = psn_data.k_str_empty_trim("cage") {
            warn!("update side stock: cage");

            if let Err(e) = update_side(line, &side_mod, cage_sn, 1, false, client).await {
                warn!("error update side: {:?}", e);
            }

            warn!("update all stock: cage");

            let opt = json!({
                "tab": "consume",
                "psn": psn,
                "wsn": "hand",
                "data": {
                    "picknum": 1,
                }
            });

            if let Err(e) = update_stock(line, &opt, cage_sn, "cage", 1, client).await {
                warn!("error update stock: {:?}", e);
            }
        }

        if let Some(ball_sn) = psn_data.k_str("bsn") {
            let ball_num = psn_data.ck_i64("num")?;

            warn!("update side stock: ball");

            if let Err(e) = update_side(line, &side_mod, ball_sn, ball_num, false, client).await {
                warn!("error update side: {:?}", e);
            }

            warn!("update all stock: ball");

            let opt = json!({
                "tab": "consume",
                "psn": psn,
                "wsn": "hand",
                "data": {
                    "picknum": ball_num,
                }
            });

            if let Err(e) = update_stock(line, &opt, ball_sn, &mt_mod, ball_num, client).await {
                warn!("error update stock: {:?}", e);
            }
        }

        Ok(())
    }

    #[instrument(skip_all, name = "do_boxpack")]
    async fn do_boxpack(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_sn = data.ck_str("box")?;

        let cmd = json!({ "line": line });

        let body = client
            .cfgdb(&cmd, &format!("file/list/box_{box_sn}"))
            .await?;
        let body = body.ck_array("data")?;

        let mut psn_vec = Vec::new();

        for d in body.iter() {
            let psn = d.ck_str("sn")?;
            if psn == "attr" {
                continue;
            }

            warn!("get psn data");

            let cmd = json!({ "line": line, "k": psn });

            let psn_data = client.cfgdb(&cmd, "psn/get").await?;
            let psn_data = psn_data.ck_obj("data")?;

            let insn = psn_data.ck_str("insn")?;

            psn_vec.push(json!({
                "sn": psn,
                "boxpack": "hand",
                "boxpair": "",
                "boxin": "",
                "boxout": "",
            }));

            let _ = self.down_mt(line, psn_data, client).await;

            let cmd = json!({ "line": line, "k": insn});
            let _ = client.cfgdb(&cmd, "inner/rm").await;

            let cmd = json!({ "line": line, "k": psn });
            let _ = client.cfgdb(&cmd, "outer/rm").await;
        } // for

        warn!("update psn for boxpack");

        let cmd = json!({ "line": line, "v": psn_vec });
        let _ = client.cfgdb(&cmd, &format!("psn/mmd")).await?;

        warn!("remove psn from boxpair");

        let cmd = json!({ "line": line });
        let _ = client
            .cfgdb(&cmd, &format!("file/rmfile/box_{box_sn}"))
            .await;

        ok!()
    }

    async fn pair_data(
        &self,
        line: &str,
        tp: &str,
        tech: &str,
        pbox: &Option<HashSet<String>>,
        client: &Client,
    ) -> Result<VecV, Value> {
        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, &format!("{tp}/list")).await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let tsn = match d.k_str("tech") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let sn = match d.k_str("sn") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            if tech != tsn {
                continue;
            }

            if let Some(pb) = pbox {
                if !pb.contains(sn) {
                    continue;
                }
            }

            let (dia, dt, width) = match self.get_val(d) {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            ret.push(json!({
                "sn": sn,
                "dia": self.round_dia(dia),
                "dt": dt,
                "width": width,
            }));
        }

        Ok(ret)
    }

    #[instrument(skip_all, name = "do_nodata")]
    async fn do_nodata(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tsn = data.ck_str("tsn")?;
        let usn = match data.k_str_empty_trim("usn") {
            Some(v) => v,
            None => "",
        };

        let pbox = match data.k_bool("pbox") {
            Some(v) => v,
            None => false,
        };

        let mut pbox_outer = None;
        let mut pbox_inner = None;

        if pbox {
            warn!("nodata step: load pbox");

            let mut outer = HashSet::new();
            let mut inner = HashSet::new();

            let _ = self
                .get_pbox(line, usn, &mut outer, &mut inner, client)
                .await;

            pbox_outer = Some(outer);
            pbox_inner = Some(inner);
        }

        let cmd = vec![("outer", pbox_outer), ("inner", pbox_inner)];

        let mut ret = HashMap::new();

        for (k, pbox) in cmd.iter() {
            if let Ok(body) = self.pair_data(line, k, tsn, pbox, client).await {
                ret.insert(k, body);
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_nopair")]
    async fn do_nopair(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tsn = data.ck_str("tsn")?;
        let usn = match data.k_str_empty_trim("usn") {
            Some(v) => v,
            None => "",
        };

        let pbox = match data.k_bool("pbox") {
            Some(v) => v,
            None => false,
        };

        let mut pbox_outer = None;
        let mut pbox_inner = None;

        if pbox {
            warn!("nopair step: load pbox");

            let mut outer = HashSet::new();
            let mut inner = HashSet::new();

            let _ = self
                .get_pbox(line, usn, &mut outer, &mut inner, client)
                .await;

            pbox_outer = Some(outer);
            pbox_inner = Some(inner);
        }

        let cmd = vec![("outer", &pbox_outer), ("inner", &pbox_inner)];

        let mut ret = HashMap::new();

        for (k, pbox) in cmd.iter() {
            if let Ok(body) = self.pair_data(line, k, tsn, pbox, client).await {
                let mut tmp = HashMap::<String, VecV>::new();
                for d in body.iter() {
                    let d = d.ckobj()?;

                    let dia = d.ck_f64("dia")? as i64;
                    let dt = d.ck_f64("dt")?;
                    let width = d.ck_f64("width")?;

                    let tk = dia.to_string();
                    let cmd = json!({
                        "dt": dt,
                        "width": width,
                    });

                    if let Some(v) = tmp.get_mut(&tk) {
                        v.push(cmd);
                    } else {
                        tmp.insert(tk, vec![cmd]);
                    }
                }

                if !tmp.is_empty() {
                    ret.insert(k, tmp);
                }
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_trydeploy")]
    async fn do_trydeploy(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;
        let val = data.ck_obj("v")?;

        let tsn = val.ck_str("tsn")?;
        let cage_sn = val.ck_str("cage")?;
        let pnum = val.ck_i64("num")?;
        let dw = val.ck_f64("dw")?;

        warn!("deploy step: load pair");

        let psn_set = load_ring("try", tsn, line, client, None).await?;

        let mut psn_vec = Vec::new();
        let mut task_val = HashMap::new();

        let mut inner = HashSet::new();
        let mut outer = HashSet::new();

        warn!("deploy step: sync box with psn");

        let _ = self.sbox(line, &mut inner, &mut outer, client).await;

        warn!("deploy step: calc psn");

        for (_, data) in psn_set.iter() {
            let data = data.ckobj()?;

            let sn = data.ck_str("sn")?;
            let insn = data.ck_str("insn")?;

            let cmd = json!({
                "line": line,
                "k": sn
            });

            if let Ok(_) = client.cfgdb(&cmd, "psn/get").await {
                warn!("deploy step: psn repeat: {sn}");
                continue;
            }

            if !inner.contains(insn) || !outer.contains(sn) {
                continue;
            }

            let mut psn_val = data.clone();

            psn_val.remove("batch");
            psn_val.insert("tech".to_string(), json!(tsn));
            psn_val.insert("cage".to_string(), json!(cage_sn));
            psn_val.insert("dw".to_string(), json!(dw));

            psn_vec.push(json!(psn_val));

            let len = psn_vec.len() as i64;
            if len >= pnum {
                break;
            }
        }

        if psn_vec.is_empty() {
            throw_err!(ECode::EmptyData, "no psn");
        }

        warn!("deploy step: gen task sn");

        let cmd = json!({
            "line": line,
            "rule": "task",
        });
        let body = client.cfgdb(&cmd, "code/gen").await?;
        let task_sn = body.ck_obj("data")?.ck_str("psn")?;

        let pstart = base::cur_time("%Y-%m-%d %H:%M:%S");
        let pend = self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?;

        task_val.insert("sn", json!(task_sn));
        task_val.insert("pstart", json!(pstart));
        task_val.insert("pend", json!(pend));
        task_val.insert("pnum", json!(pnum));
        task_val.insert("tech", json!(tsn));
        task_val.insert("state", json!("open"));
        task_val.insert("cage", json!(cage_sn));
        task_val.insert("dw", json!(dw));

        warn!("deploy step: create task");

        let cmd = json!({
            "line": line,
            "v": task_val,
        });
        let _ = client.cfgdb(&cmd, "task/add").await?;

        warn!("deploy step: add psn");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": psn_vec,
        });
        let _ = client.cfgdb(&cmd, "psn/add").await?;

        warn!("deploy step: remove psn from outer and inner");

        let _ = self.clear_deploy(line, "try", tsn, &psn_vec, client).await;

        ok!()
    }

    #[instrument(skip_all, name = "do_deploy")]
    async fn do_deploy(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;
        let val = data.ck_obj("v")?;

        let tsn = val.ck_str("tsn")?;
        let batch_sn = val.ck_str("sn")?;
        let cage_sn = val.ck_str("cage")?;
        let pnum = val.ck_i64("num")?;

        let cmd = json!({
            "line": line,
            "k": tsn,
        });

        let tech_body = client.cfgdb(&cmd, "tech/get").await?;
        let ball_num = tech_body.ck_obj("data")?.ck_i64("num")?;

        warn!("deploy step: load pair");

        let psn_set = load_ring("pair", tsn, line, client, None).await?;

        let mut psn_vec = Vec::new();
        let mut task_val = HashMap::new();

        let mut inner = HashSet::new();
        let mut outer = HashSet::new();

        warn!("deploy step: sync box with psn");

        let _ = self.sbox(line, &mut inner, &mut outer, client).await;

        warn!("deploy step: calc psn");

        for (_, data) in psn_set.iter() {
            let data = data.ckobj()?;

            let sn = data.ck_str("sn")?;
            let insn = data.ck_str("insn")?;

            let cmd = json!({
                "line": line,
                "k": sn
            });

            if let Ok(_) = client.cfgdb(&cmd, "psn/get").await {
                warn!("deploy step: psn repeat: {sn}");
                continue;
            }

            if !inner.contains(insn) || !outer.contains(sn) {
                continue;
            }

            let batch = match data.k_array("batch") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let mut find = false;
            let mut psn_val = data.clone();

            psn_val.remove("batch");
            psn_val.insert("tech".to_string(), json!(tsn));
            psn_val.insert("cage".to_string(), json!(cage_sn));

            for d in batch.iter() {
                let d = d.ckobj()?;
                let sn = d.ck_str("sn")?;

                if sn != batch_sn {
                    continue;
                }

                for (k, v) in d.iter() {
                    if k == "sn" {
                        psn_val.insert("bsn".to_string(), json!(batch_sn));
                        continue;
                    }

                    psn_val.insert(k.to_string(), v.clone());
                }

                task_val.insert("ballch", json!(d));
                find = true;

                break;
            }

            if !find {
                continue;
            }

            psn_val.insert("num".to_string(), json!(ball_num));

            psn_vec.push(json!(psn_val));

            let len = psn_vec.len() as i64;
            if len >= pnum {
                break;
            }
        }

        warn!("deploy 2");

        if psn_vec.is_empty() {
            throw_err!(ECode::EmptyData, "no psn");
        }

        warn!("deploy step: gen task sn");

        let cmd = json!({
            "line": line,
            "rule": "task",
        });
        let body = client.cfgdb(&cmd, "code/gen").await?;
        let task_sn = body.ck_obj("data")?.ck_str("psn")?;

        warn!("deploy 3");

        let pstart = base::cur_time("%Y-%m-%d %H:%M:%S");
        let pend = self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?;

        task_val.insert("sn", json!(task_sn));
        task_val.insert("pstart", json!(pstart));
        task_val.insert("pend", json!(pend));
        task_val.insert("pnum", json!(pnum));
        task_val.insert("tech", json!(tsn));
        task_val.insert("state", json!("open"));
        task_val.insert("bsn", json!(batch_sn));
        task_val.insert("cage", json!(cage_sn));

        warn!("deploy step: create task");

        let cmd = json!({
            "line": line,
            "v": task_val,
        });
        let _ = client.cfgdb(&cmd, "task/add").await?;

        warn!("deploy step: add psn");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": psn_vec,
        });
        let _ = client.cfgdb(&cmd, "psn/add").await?;

        warn!("deploy step: remove psn from outer and inner");

        let _ = self.clear_deploy(line, "pair", tsn, &psn_vec, client).await;

        ok!()
    }

    async fn clear_deploy(
        &self,
        line: &str,
        fname: &str,
        tsn: &str,
        psn: &Vec<Value>,
        client: &Client,
    ) -> RNull {
        let cmd = json!({
            "line": line,
            "v": psn
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/rmset/{fname}_{tsn}"))
            .await;

        let mut inner = Vec::new();
        let mut outer = Vec::new();

        for d in psn.iter() {
            let d = d.ckobj()?;

            if let Some(v) = d.k_str("sn") {
                outer.push(v);
            }

            if let Some(v) = d.k_str("insn") {
                inner.push(v);
            }
        }

        if !inner.is_empty() {
            let cmd = json!({
                "line": line,
                "v": inner,
            });
            let _ = client.cfgdb(&cmd, "inner/rmset").await;
        }

        if !outer.is_empty() {
            let cmd = json!({
                "line": line,
                "v": outer,
            });
            let _ = client.cfgdb(&cmd, "outer/rmset").await;
        }

        Ok(())
    }

    async fn clear_box_tp(&self, line: &str, box_sn: &str, client: &Client) -> Resp {
        let cmd = json!({
            "line": line,
            "sn": "attr",
            "v": {
                "sn": "attr",
                "tp": "",
                "task": "",
            },
        });

        client.cfgdb(&cmd, &format!("file/md/box_{box_sn}")).await
    }

    #[instrument(skip_all, name = "do_info")]
    async fn do_info(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let cmd = json!({
            "line": line,
            "k": data.ck_str("k")?,
        });

        let psn_body = client.cfgdb(&cmd, &format!("psn/get")).await?;
        let psn_body = psn_body.ck_obj("data")?;

        let mut ret = HashMap::new();

        ret.insert("psn", psn_body.clone());

        let cmd = json!({
            "line": line,
            "k": psn_body.ck_str("task")?,
        });

        if let Ok(task_body) = client.cfgdb(&cmd, &format!("task/get")).await {
            let task_body = task_body.ck_obj("data")?;
            ret.insert("task", task_body.clone());

            let cmd = json!({
                "line": line,
                "k": task_body.ck_str("tech")?,
            });

            if let Ok(tech_body) = client.cfgdb(&cmd, &format!("tech/get")).await {
                let tech_body = tech_body.ck_obj("data")?;
                ret.insert("tech", tech_body.clone());
            }
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_appendbox")]
    async fn do_appendbox(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_val = data.ck_array("v")?;

        let mut tmp = Vec::new();

        for d in box_val.iter() {
            let d = d.ckstr()?;
            tmp.push(d.to_string());
        }

        let cmd = json!({ "line": line });
        if let Ok(body) = client.cfgdb(&cmd, &format!("field/list/box")).await {
            let body = body.ck_array("data")?;

            for d in body.iter() {
                let d = d.ckstr()?;
                tmp.push(d.to_string());
            }
        }

        tmp.sort_unstable();
        tmp.dedup();

        let cmd = json!({
            "line": line,
            "v": tmp,
        });
        client.cfgdb(&cmd, &format!("field/md/box")).await
    }

    #[instrument(skip_all, name = "do_addbox")]
    async fn do_addbox(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_sn = data.ck_str("k")?;

        let cmd = json!({ "line": line });

        if let Ok(body) = client.cfgdb(&cmd, &format!("file/read/box_{box_sn}")).await {
            let tmp = body.ck_obj("data")?;

            if tmp.len() == 1 && tmp.contains_key("attr") {
                self.clear_box_tp(line, box_sn, client).await?;
            }

            return client.cfgdb(&cmd, &format!("file/list/box_{box_sn}")).await;
        }

        let mut tmp = vec![box_sn.to_string()];

        if let Ok(body) = client.cfgdb(&cmd, &format!("field/list/box")).await {
            let body = body.ck_array("data")?;

            for d in body.iter() {
                let d = d.ckstr()?;
                tmp.push(d.to_string());
            }
        }

        tmp.sort_unstable();
        tmp.dedup();

        let cmd = json!({
            "line": line,
            "v": tmp,
        });
        client.cfgdb(&cmd, &format!("field/md/box")).await?;

        self.clear_box_tp(line, box_sn, client).await?;

        let cmd = json!({ "line": line });
        client.cfgdb(&cmd, &format!("file/list/box_{box_sn}")).await
    }

    #[instrument(skip_all, name = "do_pack")]
    async fn do_pack(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("k")?;
        let box_pack = data.ck_str("box")?;

        warn!("get psn data");

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let psn_data = client.cfgdb(&cmd, &format!("psn/get")).await?;
        let psn_data = psn_data.ck_obj("data")?;

        let task_sn = psn_data.ck_str("task")?;

        warn!("update psn for boxpack");

        let cmd = json!({
            "line": line,
            "k": psn,
            "v": {
                "boxpack": box_pack,
                "boxpair": "",
                "boxin": "",
                "boxout": "",
            }
        });

        let _ = client.cfgdb(&cmd, &format!("psn/md")).await?;

        let _ = self.down_mt(line, psn_data, client).await;

        warn!("remove psn from boxpair");

        if let Some(box_pair) = psn_data.k_str("boxpair") {
            let cmd = json!({
                "line": line,
                "sn": psn,
            });

            let _ = client
                .cfgdb(&cmd, &format!("file/rm/box_{box_pair}"))
                .await?;

            let cmd = json!({ "line": line });

            if let Ok(body) = client
                .cfgdb(&cmd, &format!("file/read/box_{box_pair}"))
                .await
            {
                let tmp = body.ck_obj("data")?;

                if tmp.len() == 1 && tmp.contains_key("attr") {
                    self.clear_box_tp(line, box_pair, client).await?;
                }
            }
        }

        warn!("add psn to boxpack");

        let cmd = json!({
            "line": line,
            "v": {
                psn: { "sn": psn },
                "attr": {
                    "tp": "pack",
                    "task": task_sn,
                },
            }
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/modify/box_{box_pack}"))
            .await?;

        ok!()
    }

    async fn do_repair(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("k")?;
        let box_pair = data.ck_str("box")?;

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let body = client.cfgdb(&cmd, &format!("psn/get")).await?;
        let body = body.ck_obj("data")?;

        let task_sn = body.ck_str("task")?;

        let cmd = json!({
            "line": line,
            "k": psn,
            "v": {
                "boxpair": box_pair,
                "boxaway": "",
            }
        });

        let _ = client.cfgdb(&cmd, &format!("psn/md")).await?;

        if let Some(box_away) = body.k_str("boxaway") {
            let cmd = json!({
                "line": line,
                "sn": psn,
            });

            let _ = client
                .cfgdb(&cmd, &format!("file/rm/box_{box_away}"))
                .await?;
        }

        let cmd = json!({
            "line": line,
            "v": {
                psn: {
                    "sn": psn,
                },
                "attr": {
                    "tp": "pair",
                    "task": task_sn,
                }
            }
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/modify/box_{box_pair}"))
            .await?;

        ok!()
    }

    async fn do_ng(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("k")?;

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let body = client.cfgdb(&cmd, &format!("psn/get")).await?;
        let body = body.ck_obj("data")?;

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let _ = client.cfgdb(&cmd, &format!("psn/rm")).await?;

        if let Some(box_pair) = body.k_str_empty_trim("boxpair") {
            let _ = client.cfgdb(&cmd, &format!("file/rm/box_{box_pair}")).await;
        }

        if let Some(task_sn) = body.k_str_empty_trim("task") {
            let _ = try_rm_task(line, task_sn, client).await;
        }

        ok!()
    }

    async fn do_join(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("k")?;
        let box_pair = data.ck_str("box")?;

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let body = client.cfgdb(&cmd, &format!("psn/get")).await?;
        let body = body.ck_obj("data")?;

        let box_out = body.ck_str("boxout")?;
        let box_in = body.ck_str("boxin")?;
        let task_sn = body.ck_str("task")?;
        let insn = body.ck_str("insn")?;

        let cmd = json!({
            "line": line,
            "k": psn,
            "v": {
                "boxpair": box_pair,
                "boxin": "",
                "boxout": "",
            }
        });

        let _ = client.cfgdb(&cmd, &format!("psn/md")).await?;

        warn!("pair log");

        if let Some(tsn) = body.k_str_empty_trim("tech") {
            let _ = log_pack(line, "pairlog", psn, tsn, client).await;
        }

        let cmd = json!({
            "line": line,
            "sn": psn,
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/rm/box_{box_out}"))
            .await?;

        let cmd = json!({
            "line": line,
            "sn": insn,
        });

        let _ = client.cfgdb(&cmd, &format!("file/rm/box_{box_in}")).await?;

        let cmd = json!({
            "line": line,
            "v": {
                psn: {
                    "sn": psn,
                },
                "attr": {
                    "tp": "pair",
                    "task": task_sn,
                }
            }
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/modify/box_{box_pair}"))
            .await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_unpsn")]
    async fn do_unpsn(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_sn = data.ck_str("k")?;

        let (tp, psn_set) = box_psn(line, box_sn, client).await?;
        if tp != "outer" {
            return ok!("nouter");
        }

        warn!("unpsn: get task");

        let mut task = HashMap::new();

        for d in psn_set.iter() {
            let cmd = json!({ "line": line, "k": d});

            let psn_body = match client.cfgdb(&cmd, "psn/get").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let psn_body = psn_body.ck_obj("data")?;
            let task_sn = psn_body.ck_str("task")?;

            task.entry(task_sn.to_string())
                .or_insert(Vec::new())
                .push(psn_body.clone());
        }

        for (k, v) in task.iter() {
            let mut psn_data = Vec::new();
            let mut psn_back = Vec::new();

            for d in v.iter() {
                let sn = d.ck_str("sn")?;

                psn_back.push(d);
                psn_data.push(sn);
            }

            if psn_back.is_empty() {
                continue;
            }

            warn!("unpsn: back data");

            let cmd = json!({ "line": line, "v": psn_back });

            let _ = client.cfgdb(&cmd, &format!("field/mdnocache/bk_{k}")).await;

            let cmd = json!({
                "line": line,
                "k": k,
                "v": psn_data,
            });

            if let Err(e) = client.cfgdb(&cmd, "task/rmpsn").await {
                warn!("unpsn error: {:?}", e);
                continue;
            }

            let _ = try_rm_task(line, k, client).await;

            let mut inner = Vec::new();
            let mut outer = Vec::new();

            for d in psn_back.iter() {
                let _ = self.add_part(line, "inner", d, &mut inner, client).await;
                let _ = self.add_part(line, "outer", d, &mut outer, client).await;
            }

            if !inner.is_empty() {
                warn!("unpsn: readd to inner");

                let cmd = json!({ "line": line, "v": inner });
                if let Err(e) = client.cfgdb(&cmd, &format!("inner/madd")).await {
                    warn!("unpsn inner error: {:?}", e);
                }
            }

            if !outer.is_empty() {
                warn!("unpsn: readd to outer");

                let cmd = json!({ "line": line, "v": outer });
                if let Err(e) = client.cfgdb(&cmd, &format!("outer/madd")).await {
                    warn!("unpsn inner error: {:?}", e);
                }
            }
        }

        ok!()
    }

    #[instrument(skip_all, name = "do_rebind")]
    async fn do_rebind(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let task_sn = data.ck_str("k")?;

        warn!("rebind: get task and psn");

        let cmd = json!({
            "line": line,
            "k": task_sn,
        });

        let psn_body = match client.cfgdb(&cmd, "psn/list").await {
            Ok(v) => v,
            Err(_) => {
                warn!("rebind: no data");

                let cmd = json!({
                    "line": line,
                    "k": task_sn,
                });

                let _ = client.cfgdb(&cmd, "task/rm").await?;

                return ok!();
            }
        };

        let psn_body = psn_body.ck_array("data")?;

        let mut psn_data = Vec::new();
        let mut psn_back = Vec::new();

        warn!("rebind: collect data");

        for p in psn_body.iter() {
            let p = p.ckobj()?;

            let boxpack = p.k_str_empty_trim("boxpack");
            if boxpack.is_some() {
                continue;
            }

            let standout = p.k_f64("standout");
            let angle = p.k_f64("angle");
            let kia = p.k_f64("kia");

            if standout.is_some() || angle.is_some() || kia.is_some() {
                continue;
            }

            let psn = p.ck_str("sn")?;

            psn_data.push(psn);
            psn_back.push(p);
        }

        if psn_data.is_empty() {
            return ok!();
        }

        warn!("rebind: back data");

        let cmd = json!({
            "line": line,
            "v": psn_back,
        });

        let _ = client
            .cfgdb(&cmd, &format!("field/mdnocache/bk_{task_sn}"))
            .await;

        warn!("rebind: rm psn from task");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": psn_data,
        });

        let _ = client.cfgdb(&cmd, "task/rmpsn").await?;

        let _ = try_rm_task(line, task_sn, client).await;

        warn!("rebind: readd to inner and outer");

        let mut inner = Vec::new();
        let mut outer = Vec::new();

        for d in psn_back.iter() {
            let _ = self.add_part(line, "inner", d, &mut inner, client).await;
            let _ = self.add_part(line, "outer", d, &mut outer, client).await;
        }

        if !inner.is_empty() {
            warn!("rebind: readd to inner");

            let cmd = json!({ "line": line, "v": inner });
            if let Err(e) = client.cfgdb(&cmd, &format!("inner/madd")).await {
                warn!("rebind inner error: {:?}", e);
            }
        }

        if !outer.is_empty() {
            warn!("rebind: readd to outer");

            let cmd = json!({ "line": line, "v": outer });
            if let Err(e) = client.cfgdb(&cmd, &format!("outer/madd")).await {
                warn!("rebind inner error: {:?}", e);
            }
        }

        ok!()
    }

    async fn add_part(
        &self,
        line: &str,
        tp: &str,
        data: &MapSv,
        add: &mut VecV,
        client: &Client,
    ) -> RNull {
        let (ksn, kbox, kdia, kdt, kwidth) = match tp {
            "inner" => ("insn", "boxin", "india", "indt", "inwidth"),
            "outer" => ("sn", "boxout", "outdia", "outdt", "outwidth"),
            _ => {
                throw_err!(ECode::Para, "tp error");
            }
        };

        let sn = data.ck_str(ksn)?;
        let tech = data.ck_str("tech")?;
        let box_sn = match data.k_str(kbox) {
            Some(v) => v,
            None => "",
        };

        let dia = data.ck_f64(kdia)?;
        let dt = data.ck_f64(kdt)?;
        let width = data.ck_f64(kwidth)?;

        let cmd = json!({ "line": line, "k": sn });
        if let Err(_) = client.cfgdb(&cmd, &format!("{tp}/get")).await {
            add.push(json!({
                "time": self.cur_time("%Y-%m-%d %H:%M:%S"),
                "sn": sn,
                "box": box_sn,
                "dia": dia,
                "dt": dt,
                "width": width,
                "tech": tech,
            }));
        }

        Ok(())
    }

    async fn get_run_psn(&self, line: &str, client: &Client) -> Result<VecV, Value> {
        let cmd = json!({
            "line": line,
            "withpsn": true,
        });
        let mut task_body = client.cfgdb(&cmd, "task/run").await?;
        let task_body = task_body.ck_array_mut("data")?;

        let mut psn_data = Vec::new();

        for task in task_body.iter_mut() {
            let mut psn_body = match task.k_array_mut("psn") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            psn_data.append(&mut psn_body);
        }

        if psn_data.is_empty() {
            return Err(json!("no data"));
        }

        Ok(psn_data)
    }

    async fn find_psn(&self, line: &str, sn: &str, tp: &str, client: &Client) -> Resp {
        let body = load_ready("box", None, None, line, client).await?;

        for (box_sn, v) in body.iter() {
            let v = v.ckobj()?;

            let f_tp = match self.get_box_tp(v) {
                Some(t) => t,
                None => "away".to_string(),
            };

            if f_tp != tp {
                continue;
            }

            for (psn, d) in v.iter() {
                if psn == "attr" {
                    continue;
                }

                if psn == sn {
                    return Ok(json!({
                        "box": box_sn,
                        "v": d,
                    }));
                }
            }
        }

        Err(json!("no find"))
    }

    async fn sbox(&self, line: &str, inner: &mut HS, outer: &mut HS, client: &Client) -> RNull {
        let body = load_ready("box", None, None, line, client).await?;

        for (_, v) in body.iter() {
            let v = v.ckobj()?;

            let tp = match self.get_box_tp(v) {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            match tp.as_str() {
                "inner" => {
                    for (psn, _) in v.iter() {
                        if psn == "attr" {
                            continue;
                        }

                        inner.insert(psn.to_string());
                    }
                }
                "outer" => {
                    for (psn, _) in v.iter() {
                        if psn == "attr" {
                            continue;
                        }

                        outer.insert(psn.to_string());
                    }
                }
                _ => {
                    continue;
                }
            }
        }

        Ok(())
    }

    #[instrument(skip_all, name = "do_sbox")]
    async fn do_sbox(&self, line: &str, client: &Client) -> Resp {
        let mut inner = HashSet::new();
        let mut outer = HashSet::new();

        let _ = self.sbox(line, &mut inner, &mut outer, client).await;

        let mut r_inner = Vec::new();
        let mut r_outer = Vec::new();

        for d in inner.iter() {
            r_inner.push(d);
        }

        for d in outer.iter() {
            r_outer.push(d);
        }

        ok!(json!({
            "inner": r_inner,
            "outer": r_outer,
        }))
    }

    #[instrument(skip_all, name = "do_remove")]
    async fn do_remove(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_tp = data.ck_str("tp")?;
        let sn = data.ck_str("sn")?;
        let is_pack = match data.k_bool("pack") {
            Some(v) => v,
            None => false,
        };

        warn!("remove step: try remove psn from box");

        if let Ok(v) = self.find_psn(line, sn, box_tp, client).await {
            warn!("remove step: get psn from box, remove it");

            let boxsn = v.ck_str("box")?;

            let cmd = json!({
                "line": line,
                "sn": sn,
            });

            let _ = client.cfgdb(&cmd, &format!("file/rm/box_{boxsn}")).await?;
        }

        if is_pack {
            warn!("remove step: psn is pack");

            let cmd = json!({
                "line": line,
                "k": sn,
            });

            if let Ok(_) = client.cfgdb(&cmd, "psn/get").await {
                let cmd = json!({
                    "line": line,
                    "k": sn,
                    "v": {
                        "boxpack": "",
                        "boxpair": "",
                    }
                });

                let _ = client.cfgdb(&cmd, "psn/md").await?;
            }

            return ok!();
        }

        warn!("remove step: psn is not pack");

        let is_outer = match box_tp {
            "outer" => true,
            "inner" => false,
            _ => {
                throw_err!(ECode::Para, "box type error");
            }
        };

        warn!("remove step: get run psn");

        let psn_body = self.get_run_psn(line, client).await?;

        let mut md = HashMap::new();
        let mut out_sn = "";

        for p in psn_body.iter() {
            let p = p.ckobj()?;
            out_sn = p.ck_str("sn")?;

            let t_sn = if is_outer { out_sn } else { p.ck_str("insn")? };

            if sn != t_sn {
                continue;
            }

            if is_outer {
                if let Some(_) = p.k_str_empty_trim("boxout") {
                    md.insert("boxout", "");
                }
            } else {
                if let Some(_) = p.k_str_empty_trim("boxin") {
                    md.insert("boxin", "");
                }
            }

            break;
        }

        warn!("remove step: update psn");

        if !md.is_empty() {
            let cmd = json!({
                "line": line,
                "k": out_sn,
                "v": md
            });

            let _ = client.cfgdb(&cmd, "psn/md").await?;
        }

        ok!()
    }

    async fn handbox(&self, line: &str, client: &Client) -> Resp {
        let box_body = self.get_box(line, client).await?;
        let psn_body = self.get_run_psn(line, client).await?;

        let mut psn_data = Vec::new();

        for p in psn_body.iter() {
            let p = p.ckobj()?;

            let out_psn = match p.k_str("sn") {
                Some(v) => v,
                None => "",
            };

            let in_psn = match p.k_str("insn") {
                Some(v) => v,
                None => "",
            };

            psn_data.push((out_psn.to_string(), in_psn.to_string()));
        }

        if psn_data.is_empty() {
            return ok!();
        }

        let mut md = Vec::new();

        for (out_psn, in_psn) in psn_data.iter() {
            let mut tmp = HashMap::new();

            if !out_psn.is_empty() {
                let out_sn = format!("outer{out_psn}");

                if let Some(v) = box_body.k_str(&out_sn) {
                    tmp.insert("boxout", v);
                } else {
                    tmp.insert("boxout", "");
                }
            }

            if !in_psn.is_empty() {
                let in_sn = format!("inner{in_psn}");
                if let Some(v) = box_body.k_str(&in_sn) {
                    tmp.insert("boxin", v);
                } else {
                    tmp.insert("boxin", "");
                }
            }

            if tmp.is_empty() {
                continue;
            }

            tmp.insert("sn", out_psn);

            md.push(tmp);
        } // for

        if md.is_empty() {
            return ok!();
        }

        for d in md.chunks(100) {
            let cmd = json!({
                "line": line,
                "v": d
            });

            let _ = client.cfgdb(&cmd, "psn/mmd").await?;
        }

        ok!()
    }

    async fn do_handbox(&self, line: &str, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        self.handbox(line, client).await
    }

    async fn get_pbox(
        &self,
        line: &str,
        usn: &str,
        outer: &mut HashSet<String>,
        inner: &mut HashSet<String>,
        client: &Client,
    ) -> RNull {
        let body = load_ready(&format!("pbox{usn}"), Some("box"), None, line, client).await?;

        for (_, v) in body.iter() {
            let v = v.ckobj()?;

            let tp = match self.get_box_tp(v) {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            for (psn, _) in v.iter() {
                if psn == "attr" {
                    continue;
                }

                if tp == "outer" {
                    outer.insert(psn.to_string());
                } else if tp == "inner" {
                    inner.insert(psn.to_string());
                }
            }
        }

        Ok(())
    }

    // {
    //     <box_tp><psn>: <box_sn>,
    // }
    async fn get_box(&self, line: &str, client: &Client) -> Result<HashMap<String, String>, Value> {
        let body = load_ready("box", None, None, line, client).await?;
        let mut ret = HashMap::new();

        for (box_sn, v) in body.iter() {
            let v = v.ckobj()?;

            let tp = match self.get_box_tp(v) {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            for (psn, _) in v.iter() {
                if psn == "attr" {
                    continue;
                }

                ret.insert(format!("{tp}{psn}"), box_sn.to_string());
            }
        }

        Ok(ret)
    }

    #[instrument(skip_all, name = "do_repair_calc")]
    async fn do_repair_calc(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let usn = match data.k_str_empty_trim("usn") {
            Some(v) => v,
            None => "",
        };

        let mut pbase = PairBase::new();

        pbase.para_repair(line, data)?;

        warn!("repair calc: clear pair");

        let _ = self.clear_pair(line, "re_pair", client).await;

        warn!("repair calc: get tech");

        pbase.get_tech(client, true).await?;

        warn!("repair calc: load ball");

        let ball = pbase.load_ball(client).await?;

        warn!("repair calc: load repair data");

        let cmd = json!({ "line": line, "k": pbase.tsn() });

        let psn_body = client.cfgdb(&cmd, "away/listpsn").await?;
        let psn_body = psn_body.ck_array("data")?;

        warn!("psn_body: {:?}", psn_body.len());

        let mut pbox_body = HashSet::new();
        let pbox = pbase.pbox();

        if pbox {
            warn!("repair calc: load pbox");
            let _ = load_box(line, &mut pbox_body, &format!("rbox{usn}"), client).await;
        }

        let mut pair = MapSv::new();

        warn!("step: start pair");

        for d in psn_body.iter() {
            let d = d.ckobj()?;

            let psn = d.ck_str("sn")?;

            if pbox {
                if !pbox_body.contains(psn) {
                    continue;
                }
            }

            let g_standout = match d.k_f64("standout") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let g_std = match d.k_f64("std") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let g_sub = match d.k_f64("sub") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let g_dw = if pbase.mt() == "T" {
                g_std + g_sub
            } else {
                g_sub
            };

            let mut ball_batch = Vec::new();

            for (batch_sn, dw, std, sub) in ball.iter() {
                let n_b = g_standout + (dw - g_dw) * pbase.dw();
                let br = pbase.br();

                if br.0 {
                    if !self.is_range(n_b, br.1, br.2) {
                        continue;
                    }
                }

                let cmd = json!({
                    "sn": batch_sn,
                    "ball": pbase.ball(),
                    "dw": dw,
                    "std": std,
                    "sub": sub,
                    "mt": pbase.mt(),
                    "n_b": n_b,
                    "g_dw": g_dw,
                    "g_std": g_std,
                    "g_sub": g_sub,
                });

                ball_batch.push(cmd);
            }

            if ball_batch.is_empty() {
                continue;
            }

            self.insert_pair_data(
                &mut pair,
                pbase.tsn(),
                psn,
                json!({
                    "sn": psn,
                    "ball": pbase.ball(),
                    "num": pbase.num(),
                    "batch": ball_batch,
                }),
            );
        } //for

        warn!("step: save pair");

        save_data(line, "re_pair", &pair, client).await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_try")]
    async fn do_try(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let mut pbase = PairBase::new();

        pbase.para(line, data)?;

        let _ = pbase.save_his("handcalc", data, client).await;

        let _ = self.clear_pair(line, "try", client).await;
        let _ = self.clear_pair(line, "pair", client).await;

        pbase.get_tech(client, false).await?;
        pbase.load_new(client).await?;

        let ball = pbase.load_try(client).await?;

        /* <tech>: {
         *     <outer psn>: {...}
         * }
         */
        let mut pair = MapSv::new();
        let mut all_log = Vec::new();

        all_log.push(format!(
            "out_tech: {:?}, in_tech: {:?}",
            pbase.out_tech.len(),
            pbase.in_tech.len()
        ));

        pbase.box_count(&mut all_log);

        warn!("step: start pair");

        // outer psn is the task's psn
        for (out_psn, out_data) in pbase.out_tech.iter() {
            if !pbase.is_outbox(out_psn) {
                continue;
            }

            let out = match pbase.out_val(out_data) {
                Some(v) => v,
                None => {
                    all_log.push(format!("缺失外圈数据: {:?}", out_psn));
                    continue;
                }
            };

            if !pbase.ck_outdia(&out, &mut all_log) {
                continue;
            }

            let mut find = "".to_string();
            let mut log = HashMap::new();

            // scan inner psn for pair
            for (in_psn, in_data) in pbase.in_tech.iter() {
                if !pbase.is_inbox(in_psn) {
                    continue;
                }

                // d, di, B
                let inval = match pbase.in_val(in_data) {
                    Some(v) => v,
                    None => {
                        log.insert("india".to_string(), format!("缺失内圈数据: {:?}", in_psn));
                        continue;
                    }
                };

                if !pbase.ck_india(&inval, &mut log) {
                    continue;
                }

                if !pbase.ck_dia(&out, &inval, &mut log) {
                    continue;
                }

                if !pbase.ck_width(&out, &inval, &mut log) {
                    continue;
                }

                let ball_batch = match pbase.try_ball(&ball, &out, &inval) {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                let val = pbase.pair_val(out_psn, in_psn, &out, &inval, &ball_batch)?;

                self.insert_pair_data(&mut pair, pbase.tsn(), out_psn, json!(val));

                find = in_psn.to_string();

                log.clear();
                break;
            } //for

            if !log.is_empty() {
                for (_, v) in log.iter() {
                    all_log.push(v.clone());
                }
            }

            if all_log.len() > 800 {
                all_log.clear();
            }

            if !find.is_empty() {
                pbase.in_tech.remove(&find);
            }
        } //for

        let _ = pbase.save_log("thc", all_log, client).await;

        warn!("step: save try");

        save_data(line, "try", &pair, client).await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_calc")]
    async fn do_calc(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let mut pbase = PairBase::new();

        pbase.para(line, data)?;

        let _ = pbase.save_his("handcalc", data, client).await;

        let _ = self.clear_pair(line, "try", client).await;
        let _ = self.clear_pair(line, "pair", client).await;

        pbase.get_tech(client, false).await?;

        let ball = pbase.load_ball(client).await?;

        pbase.load_new(client).await?;

        /* <tech>: {
         *     <outer psn>: {...}
         * }
         */
        let mut pair = MapSv::new();
        let mut all_log = Vec::new();

        all_log.push(format!(
            "out_tech: {:?}, in_tech: {:?}",
            pbase.out_tech.len(),
            pbase.in_tech.len()
        ));

        pbase.box_count(&mut all_log);

        warn!("step: start pair");

        // outer psn is the task's psn
        for (out_psn, out_data) in pbase.out_tech.iter() {
            if !pbase.is_outbox(out_psn) {
                continue;
            }

            let out = match pbase.out_val(out_data) {
                Some(v) => v,
                None => {
                    all_log.push(format!("缺失外圈数据: {:?}", out_psn));
                    continue;
                }
            };

            if !pbase.ck_outdia(&out, &mut all_log) {
                continue;
            }

            let mut find = "".to_string();
            let mut log = HashMap::new();

            // scan inner psn for pair
            for (in_psn, in_data) in pbase.in_tech.iter() {
                if !pbase.is_inbox(in_psn) {
                    log.insert("no_inbox".to_string(), format!("内圈不存在: {:?}", in_psn));
                    continue;
                }

                // d, di, B
                let inval = match pbase.in_val(in_data) {
                    Some(v) => v,
                    None => {
                        log.insert("india".to_string(), format!("缺失内圈数据: {:?}", in_psn));
                        continue;
                    }
                };

                if !pbase.ck_india(&inval, &mut log) {
                    continue;
                }

                if !pbase.ck_dia(&out, &inval, &mut log) {
                    continue;
                }

                if !pbase.ck_width(&out, &inval, &mut log) {
                    continue;
                }

                let ball_batch = match pbase.ck_ball(&ball, &out, &inval, &mut log) {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                let val = pbase.pair_val(out_psn, in_psn, &out, &inval, &ball_batch)?;

                self.insert_pair_data(&mut pair, pbase.tsn(), out_psn, json!(val));

                find = in_psn.to_string();

                log.clear();
                break;
            } //for

            if !log.is_empty() {
                for (_, v) in log.iter() {
                    all_log.push(v.clone());
                }
            }

            if all_log.len() > 800 {
                all_log.drain(400..);
            }

            if !find.is_empty() {
                pbase.in_tech.remove(&find);
            }
        } //for

        let _ = pbase.save_log("hc", all_log, client).await;

        save_data(line, "pair", &pair, client).await?;

        ok!()
    }

    #[instrument(skip_all, name = "clear_pair")]
    async fn clear_pair(&self, line: &str, fname: &str, client: &Client) -> Result<(), Value> {
        warn!("step: clear pair");

        let cmd = json!({ "line": line });

        let body = client.cfgdb(&cmd, &format!("field/list/{fname}")).await?;
        let body = body.ck_array("data")?;

        for d in body.iter() {
            let d = d.ckstr()?;

            let _ = client
                .cfgdb(&cmd, &format!("file/rmfile/{fname}_{d}"))
                .await;
        }

        let _ = client.cfgdb(&cmd, "field/rmfile/{fname}").await?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn psn_test_m32() {
        let v = Pair;

        assert_eq!(v.round_dia(0.0015), 0.002);
        assert_eq!(v.round_dia(0.0016), 0.002);
        assert_eq!(v.round_dia(0.0014), 0.001);
        assert_eq!(v.round_dia(-0.0014), -0.001);
        assert_eq!(v.round_dia(-0.0015), -0.001);
        assert_eq!(v.round_dia(-0.00152), -0.001);
        assert_eq!(v.round_dia(-0.00156), -0.002);
        assert_eq!(v.round_dia(-0.0016), -0.002);
    }
}
